package com.shelpe.services.algorithm.controller;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.shelpe.services.algorithm.development.DevelopmentSustainRule;
import com.shelpe.services.algorithm.development.DevelopmentSustainer;
import com.shelpe.services.algorithm.entity.jpa.Adgroup;
import com.shelpe.services.algorithm.entity.jpa.DevelopmentKeyword;
import com.shelpe.services.algorithm.entity.jpa.DevelopmentPlan;
import com.shelpe.services.algorithm.repository.jpa.AdgroupRepository;
import com.shelpe.services.algorithm.repository.jpa.DevelopmentKeywordRepository;
import com.shelpe.services.algorithm.repository.jpa.DevelopmentPlanRepository;
import com.shelpe.services.algorithm.sustain.SustainRule;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Controller
@RequestMapping("/algorithmservice")
@Slf4j
public class DevelopmentController {
	@Autowired
	private DevelopmentPlanRepository developmentPlanRepository;
	@Autowired
	private DevelopmentKeywordRepository developmentKeywordRepository;
	@Value("${spring.application.name}")
	private String applicationName;
	@Autowired
	private DevelopmentSustainRule sustainRule;
	@Autowired
	private AdgroupRepository adgroupRepository;

	@RequestMapping(path = "/developments/{plan_id}/@execute", method = RequestMethod.GET)
	@ResponseBody
	public DevelopmentExecutionRes executeQscore(@PathVariable("plan_id") long planID) {
		log.info("start executeQscore: {}", planID);
		DevelopmentExecutionRes res = new DevelopmentExecutionRes();
		res.setPlanID(planID);

		DevelopmentPlan plan = this.developmentPlanRepository.findOne(planID);
		if (plan == null) {
			log.error("developement plan is not found: {}", planID);
			res.setSuccess(false);
			return res;
		}
		if (plan.getStatus() != DevelopmentPlan.Status.starting) {
			log.error("the development plan is not in executing: {}", plan);
			res.setSuccess(false);
			return res;
		}

		log.debug("start to execute development plan: {}", plan);
		DevelopmentSustainer sustainer = new DevelopmentSustainer();
		sustainer.setPlan(plan);

		String error = "";
		boolean toNext = this.sustainRule.check(sustainer);
		if (toNext) {
			toNext = this.sustainRule.prepare(sustainer);
			if (toNext) {
				SustainRule.SustainStatus ss = this.sustainRule.sustain(sustainer);
				toNext = this.sustainRule.maintain(sustainer, ss);
				if (toNext) {
					toNext = this.sustainRule.mark(sustainer);
					if (!toNext) {
						error = "fail_to_mark";
					}
				} else {
					error = "fail_to_maintain";
				}
			} else {
				error = "fail_to_prepare";
			}
		} else {
			error = "fail_to_check";
		}

		log.debug("finish to execute development plan: {}", plan);
		res.setSuccess(toNext);
		if (!toNext) {
			res.setMessage(error);
		}
		return res;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class DevelopmentExecutionRes {
		private boolean success;
		private long planID;
		private String message;
	}

	@RequestMapping(path = "/users/{user_id}/developments", method = RequestMethod.POST)
	@ResponseBody
	public QscoreDevelopmentDto createQscore(@PathVariable("user_id") long userID,
			@RequestBody QscoreDevelopmentDto dto) {
		Assert.notNull(dto.getName());
		Assert.notNull(dto.getPremium());

		Date now = new Date();
		DevelopmentPlan plan = new DevelopmentPlan();
		BeanUtils.copyProperties(dto, plan);
		plan.setContainerID(dto.getAdgroupID());
		plan.setContainerType("adgroup");
		plan.setStatus(DevelopmentPlan.Status.starting);
		plan.setUserID(userID);
		plan.setExecutions(0);
		if (dto.getPremium().getType() == 1) {
			plan.setPremiumType(DevelopmentPlan.PremiumType.precentage);
		} else {
			plan.setPremiumType(DevelopmentPlan.PremiumType.digit);
		}
		plan.setLimitValve(BigDecimal.valueOf(dto.getLimitValve()));
		plan.setPremium(BigDecimal.valueOf(dto.getPremium().getValue()));
		plan.setCreatedTime(now);
		plan.setModifiedTime(now);
		this.developmentPlanRepository.save(plan);

		List<DevelopmentKeyword> keywords = new ArrayList<DevelopmentKeyword>();
		for (KeywordMapping km : dto.getKeywords()) {
			DevelopmentKeyword dk = new DevelopmentKeyword();
			BeanUtils.copyProperties(km, dk);
			dk.setPlanID(plan.getAutoID());
			dk.setTargetType(DevelopmentKeyword.TargetType.qscore);
			dk.setStatus(DevelopmentKeyword.Status.executable);
			dk.setCreatedTime(now);
			dk.setModifiedTime(now);
			keywords.add(dk);
		}
		this.developmentKeywordRepository.save(keywords);

		dto.setPlanID(plan.getAutoID());
		return dto;
	}

	@RequestMapping(path = "/users/{user_id}/developments/{plan_id}", method = RequestMethod.PUT)
	@ResponseBody
	public QscoreDevelopmentDto updateQscore(@PathVariable("user_id") long userID, @PathVariable("plan_id") long planID,
			@RequestBody QscoreDevelopmentDto dto) {
		DevelopmentPlan plan = this.developmentPlanRepository.findOne(planID);
		Date now = new Date();

		if (StringUtils.isNotBlank(dto.getName())) {
			plan.setName(dto.getName());
		}
		if (dto.getPremium().getType() == 1) {
			plan.setPremiumType(DevelopmentPlan.PremiumType.precentage);
		} else {
			plan.setPremiumType(DevelopmentPlan.PremiumType.digit);
		}
		plan.setLimitValve(BigDecimal.valueOf(dto.getLimitValve()));
		plan.setPremium(BigDecimal.valueOf(dto.getPremium().getValue()));

		this.developmentKeywordRepository.deleteByPlanID(plan.getAutoID());
		List<DevelopmentKeyword> keywords = new ArrayList<DevelopmentKeyword>();
		for (KeywordMapping km : dto.getKeywords()) {
			DevelopmentKeyword dk = new DevelopmentKeyword();
			BeanUtils.copyProperties(km, dk);
			dk.setPlanID(plan.getAutoID());
			dk.setTargetType(DevelopmentKeyword.TargetType.qscore);
			dk.setStatus(DevelopmentKeyword.Status.executable);
			dk.setCreatedTime(now);
			dk.setModifiedTime(now);
			keywords.add(dk);
		}
		this.developmentKeywordRepository.save(keywords);

		return dto;
	}

	@RequestMapping(path = "/developments/@stopAll", method = RequestMethod.GET)
	@ResponseBody
	public UpdateResponse stopPlans(@RequestParam("container_id") long containerID) {
		UpdateResponse res = new UpdateResponse();

		List<DevelopmentPlan> plans = this.developmentPlanRepository
				.findByStatusAndContainerID(DevelopmentPlan.Status.starting, containerID);
		for (DevelopmentPlan plan : plans) {
			plan.setStatus(DevelopmentPlan.Status.pause);
			plan.setModifiedTime(new Date());
			this.developmentPlanRepository.save(plan);
		}
		res.setStatus(true);

		return res;
	}

	@RequestMapping(path = "/developments/@syncLimit", method = RequestMethod.GET)
	@ResponseBody
	public UpdateResponse syncPlans(@RequestParam("container_id") long containerID,
			@RequestParam(name = "container_type", defaultValue = "adgroup") String containerType) {

		UpdateResponse res = new UpdateResponse();
		Adgroup adgroup = this.adgroupRepository.findOne(containerID);

		List<DevelopmentPlan> plans = this.developmentPlanRepository.findByStatusAndContainerIDAndContainerType(
				DevelopmentPlan.Status.starting, containerID, containerType);
		for (DevelopmentPlan plan : plans) {
			plan.setLimitValve(BigDecimal.valueOf(adgroup.getLimit()));
			plan.setModifiedTime(new Date());
			this.developmentPlanRepository.save(plan);
		}

		res.setStatus(true);
		res.setMessage("finish syncLimit");
		return res;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class UpdateResponse {
		private boolean status;
		private String message;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class QscoreDevelopmentDto {
		@JsonProperty("plan_id")
		private long planID;

		@JsonProperty("plan_name")
		private String name;
		@JsonProperty("adgroup_id")
		private long adgroupID;

		@JsonProperty("from")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
		private Date fromTime;
		@JsonProperty("to")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
		private Date toTime;

		private Premium premium;
		private int period;
		@JsonProperty("highest_value")
		private float limitValve;
		@JsonProperty("keywords")
		private List<KeywordMapping> keywords;

		public void from(DevelopmentPlan plan) {
			BeanUtils.copyProperties(plan, this);
		}
	}

	@Data
	public static class KeywordMapping {
		@JsonProperty("keyword_id")
		private long keywordID;
		private String word;
		@JsonProperty("target_qscore")
		private int target;
		@JsonProperty("current_qscore")
		private int originalValue;
	}

	@Data
	public static class Premium {
		private int type;
		private float value;
	}
}
